from typing import Any, Literal, Generator, TYPE_CHECKING, abstractmethod
import datetime
from dataclasses import dataclass

if TYPE_CHECKING:
    from lobobt.trade_calendar import Date, DateRange
    from lobobt.mcode import Code

def current_date_time():
    now = datetime.datetime.now()
    date = now.year * 10000 + now.month * 100 + now.day
    time = now.hour * 10000 + now.minute * 100 + now.second
    return date, time

class SelectStrategy(object):
    """ 选股策略接口 """

    @property
    def period_type(self) -> Literal['日', '分钟']:
        """ 返回周期类型，默认为日线 """
        return '日'

    @abstractmethod
    def onbar(self) -> 'tuple[Code, Date] | None':
        """
        判断当前周期的数据是否符合条件，不符合条件返回 None, 否则返回值可以是以下几种形式：
        - False 表示不符合条件
        - True 相当于返回 d=1
        - (code, d): 返回股票代码和日期偏移元组
        - d: 仅返回日期偏移，表示当前的股票代码
        - list[tuple[code, d]|d]: 返回多组股票代码和日期的列表
        - 这里的 code 一般为字符串形式，如 '000001.sz'
        - d 表示当前周期后的第几个交易日，int(d) > 0，即不能预测未来
        """
    def run(self, codes:list['Code | Literal["all", "sz", "sh", "bj"]'], dates:list['Date | DateRange']) -> Generator[tuple['Code', 'Date'], None, None]: # type: ignore
        '''从指定的股票列表和日期列表中筛选出符合筛选条件的股票列表
        Args:
            codes: 候选的股票列表，在测试模式下这个参数被忽略。 all sz sh bj 表示特定股票集，例如：
                - 'all' 表示所有 A 股票
                - 'sz' 表示深圳交易市场的所有股票
                - ['sh', 'bj'] 表示上海和北京交易市场的所有股票
            dates: 要测试的日期列表，在测试模式下这个参数被忽略。
                - 列表中元素可以是字符串表示的日期，例如 '2025-01-02'
                - 也可以是数字形式的日期例如 20250102
                - 也可以是一个表示日期范围的元组，例如 ('2025-01-01', '2025-01-10') 表示的日期范围
                - 日期范围包括包括前后端两个日期，除非他们不是交易日
        '''
        import lobobt.formula as F
        F.PERIOD_TYPE = self.period_type
        results = self.onbar()
        if results is None:
            return
        if not isinstance(results, list):
            results = [results]
        for r in results:
            if isinstance(r, tuple) or isinstance(r, list):
                assert len(r) == 2, r
                code = r[0]
                d = r[1]
            elif isinstance(r, bool):
                if not r:
                    continue
                code = F.CODE
                d = 1
            else:
                code = F.CODE
                d = int(r)
                assert d > 0, d
            print(f'select {code} {F.DATE}+{d}d')

@dataclass
class StockHold(object):
    '''持仓信息'''
    
    code :str
    ''' 股票代码, 例如 sz000001 '''

    volume :int
    ''' 可卖出的股数，单位 股 '''
    
    amount :float
    ''' 总成本 '''
    
    frozen :int
    '''不可卖出的股数（下一交易日自动解冻'''
    
@dataclass
class Order(object):
    '''买单或卖单'''    

    id :Any
    ''' 订单id, 创建订单时用户定义的 id, 此后可根据 id 操作订单 '''
    
    order_type :Literal['买入', '卖出']
    ''' 买单或卖单 '''
    
    code :str
    ''' 股票代码，例如 sz000001 '''
    
    order_price :int
    ''' 下单价格，单位厘 '''
    
    order_volume :int
    '''loat): 下单数量, 单位股 '''
    
    state :Literal['未成', '已成', '已撤']
    ''' 订单状态，'未成' '已成' '已撤' '''
    
    order_date :int
    ''' 下单日期 yyyymmdd 形式 '''
    
    order_time :int
    ''' 下单时间 HHMMSS 形式 '''
    
    finish_date :int
    ''' 成交/撤销日期 yyyymmdd 形式 '''
    
    finish_time :int
    ''' 成交/撤销时间 HHMMSS 形式 '''
    
    trading_price :int
    ''' 成交价格，单位厘 '''
    
    trading_volume :int
    ''' 成交数量，单位股 '''
    
    trading_fee :int
    ''' 成交手续费，单位厘 '''
    
    trading_tax :int
    ''' 成交印花税，单位厘 '''

@dataclass
class FundAccount(object):
    '''资金账户'''
    
    funds :int
    ''' 可用资金，单位厘 '''
    frozen :int
    ''' 冻结金额，单位厘 '''
    holdlist :list[StockHold] = None
    ''' 持仓列表 '''
    trading_fee :float = 0.0001
    ''' 佣金比例，例如 万分之一 0.0001 '''
    trading_tax :float = 0.001
    ''' 印花税，通常是 千分之一 0.001 '''

    orders : dict[str, Order] = None
    '''当前未成交的订单列表'''
    
    order_history : list[Order] = None
    '''所有历史订单列表，包括 已成和未成、已撤'''

    def finish(self, order_id:str) -> Order:
        '''成交订单，如果是买入，新增的部分默认为冻结'''

    def cancel(self, order_id:str) -> Order:
        '''撤单'''

    def __post_init__(self):
        if self.holdlist is None:
            self._holdlist = []
        if self.order_history is None:
            self.order_history = []
        if self.orders is None:
            self.orders = {}

class TradeStrategy(object):
    '''交易策略
    
    Attributes:
        period_type: 回测数据周期类型，day, min1, min5, min15, min30, min60
    '''

    def __init__(self, period_type:Literal['min1']='min1', fund_account:FundAccount=None):
        self.period_type = period_type
        self.fund_account = fund_account or FundAccount(funds=1000000000, frozen=0, trading_fee=0.0001)

    def on_start(self) -> None:
        '''回测开始时调用一次'''
        pass

    def on_end(self) -> None:
        '''回测结束时调用一次'''
        pass

    def on_tick(self) -> None:
        '''每个数据点调用一次'''
        pass

    def on_buy(self, order:Order) -> None:
        '''买单成交时调用一次'''
        pass

    def on_sold(self, order:Order) -> None:
        '''卖单成交时调用一次'''
        pass

    def on_canceled(self, order:Order) -> None:
        '''撤单时调用一次'''
        pass

    def buy(self, price:int, volume:int|float, order_id:str|None=None) -> str:
        '''下一个买单
        
        Args:
            price (int): 买单价格，单位 厘
            volume (int|float): 买单数量，整数表示股数，浮点数且小于等于 1 表示根据资金账户按比例计算：
                [-1,0]表示按剩余可用资金的比例计算，[0, 1]表示按初始资金的比例计算。最终下单都将按 100 取整。
            order_id (str|None, optional): 订单ID. Defaults to None 生成随机的订单ID.
        Returns:
            str: order id
        '''
        ...

    def sell(self, price:float, volume:int|float, order_id:str|None=None) -> str:
        '''下一个卖单
        
        Args:
            price (float): 订单价格
            volume (int|float): 订单股数，整数表示股数，浮点数且小于等于 1 表示根据持股数量按比例计算，
                最终下单的股数都将按 100 取整，除非全部卖出。
            order_id (str|None, optional): 订单ID. Defaults to None 生成随机的订单ID。
        Returns:
            str: order id
        '''
        ...

